#include <iostream>
#include <fstream>
#include <stdexcept>

#include <cstring>
#include <cstdlib>

#include "Loader.h"

using namespace std;

Loader::Loader(Interpreter& i)
: pool(i.pool),
  bytecode(i.bytecode),
  classes(i.classes),
  entry_point(i.entry_point),
  undefs(i.undefs),
  addr_to_name(i.addr_to_name)
{ }

void Loader::LoadModule(const std::string& filename)
{
    ifstream in(filename.c_str(), ios::in | ios::binary);

    if (!in)
        throw runtime_error("Nelze cist ze vstupniho souboru!");

    in.seekg(0, ios::end);
    size_t size = (size_t) in.tellg();
    in.seekg(0, ios::beg);

    raw.resize(size);
    in.rdbuf()->sgetn(&raw[0], size);

    if (GetRawAt<unsigned>(0) != 0xE5B1000B) // 0x0B00B1E5
    {
        cerr << "Nespravny format souboru!" << endl;
        exit(1);
    }

    ProcessInput();
}

void Loader::ProcessInput()
{
    int pool_start = 8;
    int funcs_len_start;
    int funcs_start;
    int classes_len_start;
    int classes_start;
    int bytecode_start;

    int pool_len;
    int funcs_len;
    int classes_len;

    pool_len = GetRawAt<int>(4);

    funcs_len_start = pool_start + pool_len;
    funcs_start = funcs_len_start + 4;
    funcs_len = GetRawAt<int>(funcs_len_start);

    classes_len_start = funcs_start + funcs_len;
    classes_start = classes_len_start + 4;
    classes_len = GetRawAt<int>(classes_len_start);

    bytecode_start = classes_start + classes_len;

    entry_point = GetRawAtInc<int>(bytecode_start);
    const size_t bc_size = raw.size() - bytecode_start;
    bytecode = new char[bc_size];
    memcpy(bytecode, &raw[bytecode_start], bc_size);

    ProcessStringPool(pool_start, pool_start + pool_len);
    ProcessFunctions(funcs_start, funcs_start + funcs_len);
    ProcessClasses(classes_start, classes_start + classes_len);
}

void Loader::ProcessStringPool(int start, int end)
{
    int i = start;

    while (i < end)
    {
        int size = GetRawAtInc<int>(i);
        pool.push_back(string(&raw[i], &raw[i + size]));
        i += size;
    }
}

void Loader::ProcessFunctions(int start, int end)
{
    int i = start;

    while (i < end)
    {
        /*int def_size =*/ GetRawAtInc<int>(i);
        int pool_index = GetRawAtInc<int>(i);
        char external = GetRawAtInc<char>(i);
        int bc_ptr = GetRawAtInc<int>(i);
        /*int params =*/ GetRawAtInc<int>(i);
        /*int locals =*/ GetRawAtInc<int>(i);
        if (!external)
        {
            addr_to_name.insert(AddrToNameMap::value_type(bytecode + bc_ptr, pool[pool_index]));
        }
        else
            undefs.insert(UndefinedFunctions::value_type(pool[pool_index], pool_index));
    }
}

void Loader::ProcessClasses(int start, int end)
{
    int i = start;

    while (i < end)
    {
        /*int def_size =*/ GetRawAtInc<int>(i);

        int class_pool_index = GetRawAtInc<int>(i);
        Class c;

        int fields_end = GetRawAtInc<int>(i);
        fields_end += i;
        int field_counter = 0;
        while (i < fields_end)
        {
            int pool_index = GetRawAtInc<int>(i);
            c.field_offsets.insert(FieldTable::value_type(pool_index, field_counter++));
        }

        int methods_end = GetRawAtInc<int>(i);
        methods_end += i;
        while (i < methods_end)
        {
            /*int def_size =*/ GetRawAtInc<int>(i);
            int pool_index = GetRawAtInc<int>(i);
            /*string name =*/ pool[pool_index];
            int bc_ptr = GetRawAtInc<int>(i);
            int params = GetRawAtInc<int>(i);
            /*int locals =*/ GetRawAtInc<int>(i);
            addr_to_name.insert(AddrToNameMap::value_type(bytecode + bc_ptr,
                    pool[class_pool_index] + string("::") + pool[pool_index]));
            c.vtable.insert(VTable::value_type(VTable::key_type(pool_index, params), bytecode + bc_ptr));
        }

        classes.insert(Classes::value_type(class_pool_index, c));
    }
}
